package com.qiang.util;

import java.lang.management.ManagementFactory;
import java.math.BigInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @package: com.qiang.util
 * @title: Snowflake
 * @projectName: burning
 * @description: //TODO
 * @author zhuhongqiang3
 * @date:  2020-04-15 10:03
 * @version: V1.0
 * @retrun com.qiang.util.burning
 */
public class Snowflake {
    /**
     * 纪元开始,2018-01-01
     */
    private static final long TH_EPOCH = 1514736000000L;

    private int timestampBits = 41;
    private int ipBits = 16;
    private int processIdBits = 18;
    private int sequenceBits = 8;

    @SuppressWarnings("unused")
    private long maxTimestamp = -1L ^ (-1L << timestampBits);
    private long maxIp = -1L ^ (-1L << ipBits);
    private long maxProcessId = -1L ^ (-1L << processIdBits);
    private long sequenceMask = -1L ^ (-1L << sequenceBits);

    private int processIdShift = sequenceBits;
    private int ipSfhit = sequenceBits + processIdBits;
    private int timestampShift = sequenceBits + processIdBits + ipBits;

    private long ip = 1L;
    private long processId = 1L;
    private long sequence = 1L;
    private long lastTimestamp = 1L;
    private final ReentrantLock lock = new ReentrantLock();

    public Snowflake(String ipAddr) {
        String[] ipArray = ipAddr.split("\\.");
        ip = Long.valueOf(ipArray[2]) << 8 | Long.valueOf(ipArray[3]) & maxIp;
        processId = Long.valueOf(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]) & maxProcessId;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = 1L;
        for (;;) {
            timestamp = System.currentTimeMillis();
            if (timestamp > lastTimestamp) {
                return timestamp;
            }
        }
    }

    public String nextId() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            long timestamp = System.currentTimeMillis();
            if (timestamp < TH_EPOCH) {
                throw new RuntimeException("clock is moving backwards.Reject requests before epoch!");
            }
            if (timestamp == lastTimestamp) {
                sequence = (sequence + 1) & sequenceMask;
                if (sequence == 0) {
                    timestamp = tilNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0L;
            }
            lastTimestamp = timestamp;
            long idLow = ip << ipSfhit | processId << processIdShift | sequence;
            BigInteger idHign = BigInteger.valueOf(timestamp - TH_EPOCH).shiftLeft(timestampShift);
            BigInteger id = idHign.add(BigInteger.valueOf(idLow));
            return id.toString(10);
        } finally {
            lock.unlock();
        }
    }

    public String nextIdHex() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            long timestamp = System.currentTimeMillis();
            if (timestamp < TH_EPOCH) {
                throw new RuntimeException("clock is moving backwards.Reject requests before epoch!");
            }

            if (timestamp == lastTimestamp) {
                sequence = (sequence + 1) & sequenceMask;
                if (sequence == 0) {
                    timestamp = tilNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0L;
            }
            lastTimestamp = timestamp;
            long idLow = ip << ipSfhit | processId << processIdShift | sequence;
            BigInteger idHign = BigInteger.valueOf(timestamp - TH_EPOCH).shiftLeft(timestampShift);
            BigInteger id = idHign.add(BigInteger.valueOf(idLow));
            return id.toString(16);
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Snowflake snow = new Snowflake("10.13.28.59");
        System.out.println(snow.nextId());
        System.out.println(snow.nextId());
        System.out.println(snow.nextId());
        System.out.println(snow.nextId());
        System.out.println(snow.nextId());
        System.out.println(snow.nextId());
        System.out.println(snow.nextId());
        System.out.println(snow.nextId());

        System.out.println(snow.nextIdHex());
        System.out.println(snow.nextIdHex());
        System.out.println(snow.nextIdHex());
        System.out.println(snow.nextIdHex());
        System.out.println(snow.nextIdHex());
        System.out.println(snow.nextIdHex());
        System.out.println(snow.nextIdHex());
        System.out.println(snow.nextIdHex());
    }
}
